﻿using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using OpenQA.Selenium.Appium.Enums;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Drawing;
using System.Linq;
using System.Threading;

namespace SimulationCenter.Scripts
{
    /// <summary>
    /// 脚本执行器
    ///
    /// note: Appium server must check `Relaxed Security`
    /// </summary>
    public class ScriptExecutor : IDisposable
    {
        public event EventHandler<ExecutorStatusChangedArgs> OnStatusChanged;

        private readonly string _serial;
        private ExecutorStatus _status;
        private Exception _exception;

        private static AppiumOptions _options;
        private readonly Uri _serverUri = new Uri(Const.RemoteServer);

        private AndroidDriver<AppiumWebElement> _driver;
        private Size _screenSize;

        private readonly ConcurrentQueue<SubscriptionInfo> _subscriptionInfos = new ConcurrentQueue<SubscriptionInfo>();

        /// <summary>
        /// 公众号列表
        /// </summary>
        public IReadOnlyCollection<SubscriptionInfo> Subscriptions => _subscriptionInfos.ToImmutableList();

        /// <summary>
        /// 当前正在检索的公众号
        /// </summary>
        public string CurrentSubscription { get; private set; }

        /// <summary>
        /// 当前脚本执行器状态
        /// </summary>
        public ExecutorStatus Status
        {
            get => _status;
            set
            {
                _status = value;
                FireStatusChangedEvent();
            }
        }

        public ScriptExecutor(string serial)
        {
            _serial = serial;

            _options = new AppiumOptions();
            InitializeOptions();

            Status = ExecutorStatus.Ready;
        }

        /// <summary>
        /// 执行脚本
        /// </summary>
        public void Start()
        {
            if (Status != ExecutorStatus.Ready) return;

            try
            {
                _driver = new AndroidDriver<AppiumWebElement>(_serverUri, _options, TimeSpan.FromHours(24));
                Status = ExecutorStatus.Running;

                CurrentSubscription = null;

                _driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(3);
                _screenSize = _driver.Manage().Window.Size;

                PreventSkipFlashPage();

                SwitchToContactsTab();

                MoveToSubscriptionMenu();

                EnsureCurrentSubscriptionsPage();

                CollectSubscriptions();

                ResetSubscriptionsPage();

                MoveNextSubscription();
            }
            catch (Exception e)
            {
                _exception = e;
                Status = ExecutorStatus.Error;

                _driver.Quit();
            }
        }

        /// <summary>
        /// 检索下一个公众号
        /// </summary>
        public void MoveNextSubscription()
        {
            if (!_subscriptionInfos.TryDequeue(out var subscriptionInfo))
            {
                Status = ExecutorStatus.Complete;
                return;
            }

            var nextSubscription = subscriptionInfo.Name;

            if (CurrentSubscription != null)
            {
                EnsureWebViewPage();

                var i = 10;

                while (i-- > 0 && !IsCurrentChattingPage())
                {
                    WebViewBack();

                    Thread.Sleep(TimeSpan.FromSeconds(1));
                }

                ChattingUiBack();

                EnsureCurrentSubscriptionsPage();
            }

            if (string.IsNullOrEmpty(nextSubscription))
            {
                Status = ExecutorStatus.Complete;
                return;
            }

            CurrentSubscription = nextSubscription;

            NavigateToSubscription(CurrentSubscription);
        }

        public void Dispose()
        {
            Status = ExecutorStatus.Disposed;

            _driver?.Dispose();
        }

        #region privates

        private void InitializeOptions()
        {
            _options.AddAdditionalCapability(MobileCapabilityType.PlatformName, "Android");
            _options.AddAdditionalCapability(MobileCapabilityType.DeviceName, "Android Emulator");
            _options.AddAdditionalCapability(MobileCapabilityType.AutomationName, "UIAutomator2");
            _options.AddAdditionalCapability(MobileCapabilityType.NoReset, true);
            _options.AddAdditionalCapability(MobileCapabilityType.Udid, _serial);
            _options.AddAdditionalCapability(MobileCapabilityType.NewCommandTimeout, 60 * 60 * 24);
            _options.AddAdditionalCapability("appPackage", Const.AppPackage);
            _options.AddAdditionalCapability("appActivity", Const.LauncherUi);
        }

        private void FireStatusChangedEvent()
        {
            var args = new ExecutorStatusChangedArgs(_serial, Status);

            if (_exception != null)
            {
                args.Exception = _exception;
                _exception = null;
            }

            OnStatusChanged?.Invoke(this, args);
        }

        /// <summary>
        /// 跳过 Flash 页面
        /// </summary>
        private void PreventSkipFlashPage()
        {
            while (true)
            {
                try
                {
                    _driver.FindElement(Const.FlashPageXpath);

                    Thread.Sleep(TimeSpan.FromSeconds(3));
                }
                catch (Exception)
                {
                    return;
                }
            }
        }

        /// <summary>
        /// 切换到联系人 Tab
        /// </summary>
        private void SwitchToContactsTab()
        {
            try
            {
                var tabs = _driver.FindElements(Const.TabContactsId);

                if (tabs.Any())
                    tabs[1].Click();
            }
            catch (Exception)
            {
                throw new ScriptExecutorException("切换到联系人Tab", $"未找到对应元素 -> {Const.TabContactsId}");
            }
        }

        /// <summary>
        /// 进入公众号列表
        /// </summary>
        private void MoveToSubscriptionMenu()
        {
            try
            {
                var menu = _driver.FindElement(Const.SubscriptionMenuId);
                menu.Click();

                Thread.Sleep(TimeSpan.FromSeconds(3));
            }
            catch (Exception)
            {
                throw new ScriptExecutorException("进入公众号列表", $"未找到公众号菜单元素 -> {Const.SubscriptionMenuId}");
            }
        }

        /// <summary>
        /// 确认当前为公众号列表页
        /// </summary>
        private void EnsureCurrentSubscriptionsPage()
        {
            if (!IsCurrentSubscriptionsPage())
            {
                throw new ScriptExecutorException("进入公众号列表", $"页面定位不准确 -> {Const.SubscriptionsActivity}");
            }
        }

        /// <summary>
        /// 采集所有公众号
        /// </summary>
        private void CollectSubscriptions()
        {
            ScrollSubscriptions(() =>
            {
                var elements = _driver.FindElements(Const.SubscriptionTitleId);

                foreach (var item in elements)
                {
                    if (!Subscriptions.Any(s => s.Name.Equals(item.Text, StringComparison.CurrentCultureIgnoreCase)))
                    {
                        _subscriptionInfos.Enqueue(new SubscriptionInfo {Name = item.Text});
                    }
                }
            });
        }

        /// <summary>
        /// 滚动公众号列表
        /// </summary>
        /// <param name="action"></param>
        private void ScrollSubscriptions(Action action)
        {
            var element = _driver.FindElement(Const.SubscriptionsId);
            var size = element.Size;
            var location = element.Location;
            bool atSubscriptionsBottom;

            do
            {
                action();

                var args = new Dictionary<string, object>
                {
                    ["command"] = "input swipe",
                    ["args"] = $"50 {size.Height - 3} 50 { location.Y + 3} 1000",
                    ["includeStderr"] = true,
                    ["timeout"] = 10000
                };

                _driver.ExecuteScript("mobile:shell", args);

                atSubscriptionsBottom = AtSubscriptionsBottom();

                if (atSubscriptionsBottom)
                    action();
            }
            while (!atSubscriptionsBottom);
        }

        /// <summary>
        /// 当前位置是否处于公众号列表的底部
        /// </summary>
        /// <returns></returns>
        private bool AtSubscriptionsBottom()
        {
            try
            {
                _driver.FindElement(Const.SubscriptionsBottomId);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// 重置公众号页面
        /// </summary>
        private void ResetSubscriptionsPage()
        {
            _driver.PressKeyCode(AndroidKeyCode.Back);

            MoveToSubscriptionMenu();

            EnsureCurrentSubscriptionsPage();
        }

        /// <summary>
        /// 公众号聊天页面返回
        /// </summary>
        private void ChattingUiBack()
        {
            try
            {
                var back = _driver.FindElement(Const.SubscriptionChattingBackId);
                back.Click();
            }
            catch (Exception)
            {
                // ignore
            }
        }

        /// <summary>
        /// WebView 页面返回
        /// </summary>
        private void WebViewBack()
        {
            try
            {
                var back = _driver.FindElement(Const.SubscriptionWebViewBackId);
                back.Click();
            }
            catch (Exception)
            {
                // ignore
            }
        }

        /// <summary>
        /// 将页面从公众号文章页面恢复到公众号列表页面
        /// </summary>
        private void EnsureWebViewPage()
        {
            if (!Const.SubscriptionWebViewPage.Equals(_driver.CurrentActivity,
                StringComparison.CurrentCultureIgnoreCase))
            {
                throw new ScriptExecutorException("返回公众号列表以检索下一个公众号", "当前页面不为 WebView 页面");
            }
        }

        /// <summary>
        /// 判断当前页是否为公众号列表页
        /// </summary>
        /// <returns></returns>
        private bool IsCurrentSubscriptionsPage()
        {
            return Const.SubscriptionsActivity.Equals(_driver.CurrentActivity,
                StringComparison.CurrentCultureIgnoreCase);
        }

        /// <summary>
        /// 判断当前页面是否为公众号聊天页面
        /// </summary>
        /// <returns></returns>
        private bool IsCurrentChattingPage()
        {
            return Const.SubscriptionChattingActivity.Equals(_driver.CurrentActivity,
                StringComparison.CurrentCultureIgnoreCase);
        }

        /// <summary>
        /// 当前 View 中能否检索到指定公众号
        /// </summary>
        /// <param name="subscription">公众号标题</param>
        /// <param name="element">公众号元素</param>
        /// <returns></returns>
        private bool HasSubscription(string subscription, out AppiumWebElement element)
        {
            element = null;

            try
            {
                var locator = new ByAndroidUIAutomator(string.Format(Const.SubscriptionTitle, subscription));
                element = _driver.FindElement(locator);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// 跳转到公众号详情页面
        /// </summary>
        private void MoveToProfilePage()
        {
            var profile = _driver.FindElement(Const.SubscriptionProfileId);
            profile.Click();

            Thread.Sleep(TimeSpan.FromSeconds(3));
        }

        /// <summary>
        /// 全部消息按钮是否可见
        /// </summary>
        /// <returns></returns>
        private bool IsAllMessagesButtonVisible(out AppiumWebElement element)
        {
            element = null;

            try
            {
                element = _driver.FindElement(Const.LoadAllMessagesId);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// 公众号详情页滑动至最底部
        /// </summary>
        private void ScrollToProfileBottomAndClickButton()
        {
            AppiumWebElement element;

            while (!IsAllMessagesButtonVisible(out element))
            {
                var args = new Dictionary<string, object>
                {
                    ["command"] = "input swipe",
                    ["args"] = $"50 {_screenSize.Height * 0.9 } 50 { _screenSize.Height * 0.1 } 1000",
                    ["includeStderr"] = true,
                    ["timeout"] = 10000
                };

                _driver.ExecuteScript("mobile:shell", args);

                Thread.Sleep(TimeSpan.FromSeconds(3));
            }

            element.Click();
        }

        /// <summary>
        /// 跳转到指定公众号详情页
        /// </summary>
        private void NavigateToSubscription(string subscription)
        {
            EnsureCurrentSubscriptionsPage();

            var lv = _driver.FindElement(Const.SubscriptionsId);
            var size = lv.Size;
            var location = lv.Location;

            AppiumWebElement element;

            while (!HasSubscription(subscription, out element))
            {
                Thread.Sleep(TimeSpan.FromSeconds(3));

                var args = new Dictionary<string, object>
                {
                    ["command"] = "input swipe",
                    ["args"] = $"50 {size.Height - 3} 50 { location.Y + 3} 1000",
                    ["includeStderr"] = true,
                    ["timeout"] = 10000
                };

                _driver.ExecuteScript("mobile:shell", args);

                if (AtSubscriptionsBottom())
                {
                    HasSubscription(subscription, out element);

                    if (element == null)
                        ResetSubscriptionsPage();
                }
            }

            if (element == null)
                throw new ScriptExecutorException($"查找指定公众号 {subscription}");

            element.Click();
            Thread.Sleep(TimeSpan.FromSeconds(3));

            MoveToProfilePage();

            ScrollToProfileBottomAndClickButton();
        }

        #endregion
    }
}
